/*******************************************************************************
 * Copyright (c) 2005, 2008 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Brad Reynolds - bug 164653
 *     Brad Reynolds - bug 164134, 171616
 *******************************************************************************/
package org.eclipse.core.databinding.observable.gwt;


import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import org.eclipse.core.databinding.observable.ChangeEvent;
import org.eclipse.core.databinding.observable.Diffs;
import org.eclipse.core.databinding.observable.IChangeListener;
import org.eclipse.core.databinding.observable.Realm;
import org.eclipse.core.databinding.observable.value.AbstractVetoableValue;
import org.eclipse.core.databinding.observable.value.IObservableValue;

/**
 * Modified version of JavaBeanObservableValue for GWT
 */
public abstract class GWTBeanObservableValue extends AbstractVetoableValue implements IChangeListener{
  protected Object bean;
  protected String propName;
  IObservableValue prefixObservableValue;
  
  private boolean updating = false;
  private Object oldValue;
  
  private PropertyChangeListener updateListener = new PropertyChangeListener(){
    public void propertyChange(PropertyChangeEvent evt) {
      Object newValue = evt.getNewValue();
      if ((!updating) && (evt.getPropertyName().equals(propName))){
        if (!equals(oldValue, newValue)){
          fireValueChange(Diffs.createValueDiff(oldValue, newValue));
          oldValue = newValue;
        }
      }
    }
    private boolean equals(Object a, Object b){
      if ((a == null) && (b == null)){
        return true;
      }
      if (a != null){
        return a.equals(b);
      }else{
        return b.equals(a);
      }
    }
  };
  
  public GWTBeanObservableValue(IObservableValue prefixObservableValue, Object bean, String propName) {
    this(Realm.getDefault(), prefixObservableValue, bean, propName);
  }
  public GWTBeanObservableValue(Realm realm, IObservableValue prefixObservableValue, Object bean, String propName) {
    super(realm);
    
    this.bean = bean;
    this.propName = propName;
    this.prefixObservableValue = prefixObservableValue;

    this.oldValue = doGetValue();
    
    if (bean != null){
      addPropertyChangeListener(bean, propName, updateListener);
    }

    if (prefixObservableValue != null){
      prefixObservableValue.addChangeListener(this);
    }
  }

  protected void doSetApprovedValue(Object value) {
    try {
      updating = true;
      if (bean != null){
        setBeanProperty(bean, propName, value);
      }
      oldValue = doGetValue();
    } finally {
      updating = false;
    }
  }

  protected Object doGetValue() {
    return bean == null ? null : getBeanProperty(bean, propName);
  }
  public Object getValueType() {
    return getBeanPropertyType(bean, propName);
  }

  public synchronized void dispose() {
    super.dispose();
    
    removePropertyChangeListener(bean, propName, updateListener);
  }
  
  public void handleChange(ChangeEvent event){
    Object oldBean = bean;
    if (oldBean != null){
      removePropertyChangeListener(oldBean, propName, updateListener);
    }
    
    bean = prefixObservableValue.getValue();
    if (bean != null){
      addPropertyChangeListener(bean, propName, updateListener);
    }
    
    fireValueChange(Diffs.createValueDiff(oldValue, doGetValue()));
    oldValue = doGetValue();
  }

  protected abstract void addPropertyChangeListener(Object bean, String propName, PropertyChangeListener listener);
  protected abstract void removePropertyChangeListener(Object bean, String propName, PropertyChangeListener listener);

  protected abstract Object getBeanProperty(Object bean, String propName);
  protected abstract void setBeanProperty(Object bean, String propName, Object value);
  protected abstract Object getBeanPropertyType(Object bean, String propName);
}
